/*
 * Copyright 2025 arisgo@163.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.arisgo.cloud.common.utils;

import com.google.common.collect.Lists;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * @author xu keqi
 * @since 2025-01-05
 */
public class DateUtil {

    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public static final String DF_YYYYMMDD = "yyyyMMdd";
    public static final String DF_YYYYMMDD_O = "yyyy/MM/dd";

    public static final String TIME_FORMAT = "HH:mm:ss";
    public static final String TF_HHMMSS = "HHmmss";
    public static final String TF_HHMM = "HHmm";

    public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DTF_YYYYMMDDHHMMSS_O = "yyyy/MM/dd HH:mm:ss";
    public static final String DTF_YYYYMMDDHHMM_O = "yyyy/MM/dd HH:mm";
    public static final String DTF_YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
    public static final String DTF_YYYYMMDDHHMM = "yyyyMMddHHmm";
    public static final String DTF_YYYYMMDDHHMMSS_M = "yyyy-MM-dd HH:mm:ss.SSS";

    public static final String TIME_ZONE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

    /**
     * 初始化日历
     * h m s S => 0 0 0 0
     */
    public static Calendar initCalendar() {
        return initStartTime(null);
    }

    public static Calendar initStartTime(Calendar calendar) {
        return initTime(calendar, 0, 0, 0);
    }

    public static Calendar initEndTime(Calendar calendar) {
        return initTime(calendar, 23, 59, 59);
    }

    private static Calendar initTime(Calendar calendar, int hour, int minute, int second) {
        if (null == calendar) {
            calendar = Calendar.getInstance();
        }
        calendar.set(Calendar.HOUR, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar;
    }

    /**
     * 每月第一天 XXXX-XX-01 00:00:00.000
     */
    public static Calendar monthFirstDay(Calendar calendar) {
        if (null == calendar) {
            calendar = initCalendar();
            calendar.set(Calendar.DAY_OF_MONTH, 1);
            return calendar;
        } else {
            Calendar instance = Calendar.getInstance();
            instance.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), 1);
            return instance;
        }
    }

    /**
     * 每月最后一天 XXXX-XX-XX 00:00:00.000
     */
    public static Calendar monthLastDay(Calendar calendar) {
        if (null == calendar) {
            calendar = initCalendar();
            calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
            return calendar;
        } else {
            Calendar instance = Calendar.getInstance();
            instance.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
            return instance;
        }
    }

    /**
     * 每周第一天 XXXX-XX-01 00:00:00.000
     */
    public static Calendar weekFirstDay(Calendar calendar) {
        return weekFirstDay(calendar, 0);
    }

    /**
     * 每周第一天 XXXX-XX-01 00:00:00.000
     *
     * @param num 相对calendar时间，1/下周一，2/下下周一， -1/上周一，-2/上上周一
     */
    public static Calendar weekFirstDay(Calendar calendar, int num) {
        if (null == calendar) {
            calendar = initCalendar();
        }
        int diff = 2 - calendar.get(Calendar.DAY_OF_WEEK);
        if (diff > 0) {
            diff -= 7;
        }
        diff += 7 * num;

        Calendar instance = (Calendar) calendar.clone();
        instance.add(Calendar.DAY_OF_WEEK, diff);
        return instance;
    }

    /**
     * 每周第一天 XXXX-XX-01 00:00:00.000
     */
    public static Calendar weekLastDay(Calendar calendar) {
        return weekLastDay(calendar, 0);
    }

    /**
     * 每周第一天 XXXX-XX-01 00:00:00.000
     *
     * @param num 相对calendar时间，1/下周一，2/下下周一， -1/上周一，-2/上上周一
     */
    public static Calendar weekLastDay(Calendar calendar, int num) {
        if (null == calendar) {
            calendar = initCalendar();
        }
        int diff = 8 - calendar.get(Calendar.DAY_OF_WEEK);
        if (diff >= 7) {
            diff -= 7;
        }
        diff += 7 * num;

        Calendar instance = (Calendar) calendar.clone();
        instance.add(Calendar.DAY_OF_WEEK, diff);
        return instance;
    }

    public static LocalDate weekFirstDay(LocalDate date) {
        return weekFirstDay(date, 0);
    }

    public static LocalDate weekFirstDay(LocalDate date, int num) {
        if (null == date) {
            date = LocalDate.now();
        }
        int diff = (1 - date.getDayOfWeek().getValue()) + 7 * num;
        return date.plusDays(diff);
    }

    public static LocalDate weekLastDay(LocalDate date) {
        return weekLastDay(date, 0);
    }

    public static LocalDate weekLastDay(LocalDate date, int num) {
        if (null == date) {
            date = LocalDate.now();
        }
        int diff = (7 - date.getDayOfWeek().getValue()) + 7 * num;
        return date.plusDays(diff);
    }

    public static LocalDateTime convert(Calendar calendar) {
        return calendar.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    public static LocalDate convertDate(Calendar calendar) {
        return calendar.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    public static LocalTime convertTime(Calendar calendar) {
        return calendar.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
    }

    /**
     * 日期差值
     * 计算两个日期间差了几天
     * 0: 同一天
     * >0: 开始时间小于结束时间
     * <0: 开始时间大于结束时间
     *
     * @param start 开始时间
     * @param end   结束时间
     */
    public long dateDifference(long start, long end) {
        // 符号标识位，0：同一天，1：开始时间小于结束时间，-1：开始时间大于结束时间
        int sign = 1;
        if (start > end) {
            sign = -1;
            long tmp = start;
            start = end;
            end = tmp;
        }

        Calendar beginCalendar = Calendar.getInstance();
        beginCalendar.setTimeInMillis(start);
        Calendar endCalendar = Calendar.getInstance();
        endCalendar.setTimeInMillis(end);

        int beginYear = beginCalendar.get(Calendar.YEAR);
        int finishYear = endCalendar.get(Calendar.YEAR);
        int begin = beginCalendar.get(Calendar.DAY_OF_YEAR);
        int finish = endCalendar.get(Calendar.DAY_OF_YEAR);
        if (beginYear == finishYear) {
            return (finish - begin) * sign;
        }

        int count = beginCalendar.getActualMaximum(Calendar.DAY_OF_YEAR) - begin + finish;
        int index = finishYear - beginYear;
        for (int i = 1; i < index; i++) {
            beginCalendar.add(Calendar.YEAR, 1);
            count += beginCalendar.getActualMaximum(Calendar.DAY_OF_YEAR);
        }

        return count * sign;
    }

    public static String format(Date date) {
        return format(date, DATE_TIME_FORMAT);
    }

    public static String format(Date date, String format) {
        if (null == date) {
            throw new NullPointerException("date is null");
        }
        return new SimpleDateFormat(format).format(date);
    }

    public static String format(LocalDate localDate) {
        return format(localDate, DATE_FORMAT);
    }

    public static String format(LocalDate localDate, String format) {
        return localDate.format(DateTimeFormatter.ofPattern(format));
    }

    public static String format(LocalTime localTime) {
        return format(localTime, TIME_FORMAT);
    }

    public static String format(LocalTime localTime, String format) {
        return localTime.format(DateTimeFormatter.ofPattern(format));
    }

    public static String format(LocalDateTime localDateTime) {
        return format(localDateTime, DATE_TIME_FORMAT);
    }

    public static String format(LocalDateTime localDateTime, String format) {
        return localDateTime.format(DateTimeFormatter.ofPattern(format));
    }

    public static String format(Instant instant) {
        return format(instant, DATE_TIME_FORMAT);
    }

    public static String format(Instant instant, String format) {
        return format(instant.atZone(ZoneId.systemDefault()).toLocalDateTime(), format);
    }

    public static String format(Long date) {
        return format(date, DATE_TIME_FORMAT);
    }

    public static String format(Long date, String format) {
        return format(new Date(date), format);
    }

    public static String formatWeek(LocalDate date) {
        if (date == null) {
            throw new NullPointerException("date is null");
        }
        DayOfWeek week = date.getDayOfWeek();
        return week.getDisplayName(TextStyle.SHORT, Locale.CHINA);
    }

    public static String formatWeek(Calendar date) {
        if (date == null) {
            throw new NullPointerException("date is null");
        }
        return formatWeek(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
    }

    public static Date parseDate(String date) {
        return parseDate(date, DATE_TIME_FORMAT);
    }

    public static Date parseDate(String date, String format) {
        try {
            return new SimpleDateFormat(format).parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    public static Long parseLong(String date) {
        return parseLong(date, DATE_TIME_FORMAT);
    }

    public static Long parseLong(String date, String format) {
        return parseDate(date, format).getTime();
    }

    public static LocalDate parseLocalDate(String date) {
        return parseLocalDate(date, DATE_TIME_FORMAT);
    }

    public static LocalDate parseLocalDate(String date, String format) {
        return parseDate(date, format).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    public static LocalTime parseLocalTime(String date) {
        return parseLocalTime(date, DATE_TIME_FORMAT);
    }

    public static LocalTime parseLocalTime(String date, String format) {
        return parseDate(date, format).toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
    }

    public static LocalDateTime parseLocalDateTime(String date) {
        return parseLocalDateTime(date, DATE_TIME_FORMAT);
    }

    public static LocalDateTime parseLocalDateTime(String date, String format) {
        return parseDate(date, format).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    public static Instant parseInstant(String date) {
        return parseInstant(date, DATE_TIME_FORMAT);
    }

    public static Instant parseInstant(String date, String format) {
        return parseDate(date, format).toInstant();
    }

    /**
     * 获取指定日期范围内的日期列表
     *
     * @param start 开始时间
     * @param end   结束时间
     */
    public static List<LocalDate> range(LocalDate start, LocalDate end) {
        if (null == start || null == end) {
            return Lists.newArrayList();
        }
        List<LocalDate> result = Lists.newArrayList();
        while (start.compareTo(end) < 1) {
            result.add(start);
            start = start.plusDays(1);
        }
        return result;
    }

    /**
     * 获取指定日期，num天的日期列表
     * 包含指定日期，加指定日期共num天
     *
     * @param start 开始时间
     * @param num 多少天
     */
    public static List<LocalDate> range(LocalDate start, int num) {
        if (null == start) {
            return Lists.newArrayList();
        }
        List<LocalDate> result = Lists.newArrayList();
        for (int i = 0; i < num; i++) {
            result.add(start.plusDays(i));
        }
        return result;
    }

}
